<?php
// $Id: panels_page.menu.inc,v 1.24.2.8 2008/12/28 13:21:16 sdboyer Exp $

/**
 * @file panels_page.menu.inc
 *
 * Functions responsible for constructing the panels_page menu routing &
 * overriding system.
 */

/**
 * Define and return all the static administrative pages for hook_menu().
 */
function panels_page_admin_static_menu_items() {
  $items = array();
  $admin = array(
    // TODO is 'create panel-pages' still the best name for this perm?
    // TODO we'll need to granulate this perm significantly in panels3.
    'access arguments' => array('create panel-pages'),
    'file' => 'panels_page.admin.inc',
    'description' => 'Create and administer panel-pages (complex layout pages with URLs).',
    'type' => MENU_LOCAL_TASK,
  );

  $items['admin/panels/panel-page'] = array(
    'title' => 'Panel pages',
    'page callback' => 'panels_page_list_page',
    'type' => MENU_NORMAL_ITEM,
  ) + $admin;
  $items['admin/panels/panel-page/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  ) + $admin;
  $items['admin/panels/panel-page/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'panels_page_settings',
    'weight' => -5,
  ) + $admin;
  $items['admin/panels/panel-page/add'] = array(
    'title' => 'Add',
    'page callback' => 'panels_page_add_handler',
    'weight' => 0,
  ) + $admin;
  $items['admin/panels/panel-page/import'] = array(
    'title' => 'Import',
    'page callback' => 'panels_page_import_page',
    'weight' => 5,
  ) + $admin;
  return $items;
}

function panels_page_admin_dynamic_menu_items($items = array(), $path_prefix = NULL) {
  // TODO allowing a dynamic path prefix may be superfluous
  if (is_null($path_prefix)) {
    $path_prefix = 'admin/panels/panel-page/';
  }
  $loader_arg = count(explode('/', $path_prefix)) - 1;

  $admin = array(
    'access arguments' => array('create panel-pages'),
    'file' => 'panels_page.admin.inc',
    'page arguments' => array($loader_arg),
    'type' => MENU_LOCAL_TASK,
  );

  $items[$path_prefix . '%panels_page_admin/edit'] = array(
    'title' => 'Settings',
    'page callback' => 'panels_page_edit',
    'weight' => -10,
    'type' => MENU_CALLBACK,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/settings'] = array(
    'title' => 'Settings',
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  ) + $admin;
// Alternate method for organizing the settings/advanced tabs. I think this is
// ultimately more sensible/intuitive. We'll see...
/*
  $items[$path_prefix . '%panels_page_admin/edit/settings/general'] = array(
    'title' => 'General',
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items[$path_prefix . '%panels_page_admin/edit/settings/advanced'] = array(
    'title' => 'Advanced',
    'page callback' => 'panels_page_edit_advanced',
    'weight' => -8,
  ) + $admin;*/
  $items[$path_prefix . '%panels_page_admin/edit/advanced'] = array(
    'title' => 'Advanced',
    'page callback' => 'panels_page_edit_advanced',
    'weight' => -8,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/context'] = array(
    'title' => 'Context',
    'page callback' => 'panels_page_edit_context',
    'load arguments' => array('panels_page_admin_cache_load'),
    'weight' => -6,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/layout'] = array(
    'title' => 'Layout',
    'page callback' => 'panels_page_edit_layout',
    'load arguments' => array('panels_page_admin_cache_load', $loader_arg + 3),
    'weight' => -4,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/layout/default'] = array(
    'title' => 'Default',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -20,
  );
  $items[$path_prefix . '%panels_page_admin/edit/layout-settings'] = array(
    'title' => 'Layout settings',
    'page callback' => 'panels_page_edit_layout_settings',
    'load arguments' => array('panels_page_admin_cache_load', $loader_arg + 3),
    'weight' => -2,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/layout-settings/default'] = array(
    'title' => 'Default',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -20,
  );
  $items[$path_prefix . '%panels_page_admin/edit/content'] = array(
    'title' => 'Content',
    'page callback' => 'panels_page_edit_content',
    'load arguments' => array('panels_page_admin_cache_load', $loader_arg + 3),
    'weight' => 0,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/content/default'] = array(
    'title' => 'Default',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -20,
  );
/*  $items[$path_prefix . '%panels_page_admin/edit/preview'] = array(
    'title' => 'Preview',
    'page callback' => 'panels_page_preview',
    'weight' => 2,
  ) + $admin;*/
  $items[$path_prefix . '%panels_page_admin/edit/preview'] = array(
    'title' => 'Preview',
    'page callback' => 'panels_page_preview_page',
    'page arguments' => array($loader_arg),
    'weight' => 2,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/edit/export'] = array(
    'title' => 'Export',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('panels_page_export_page', $loader_arg),
    'weight' => 4,
  ) + $admin;
  $items[$path_prefix . '%panels_page_admin/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('panels_page_delete_confirm', $loader_arg),
    'type' => MENU_CALLBACK,
  ) + $admin;
  return $items;
}

function panels_page_create_menu_structure($new_items = NULL) {
  static $items = array();
  // The first time through, we _always_ construct the menu tree.
  if (empty($items)) {
    $items = _panels_page_create_menu_structure();
  }
  if (!is_null($new_items)) {
    $items = $new_items;
  }
  return $items;
}

/**
 * Workhorse function for hook_menu_alter(); separated out here to reduce code
 * weight.
 *
 * @param array $callbacks
 */
function _panels_page_menu_alter(&$callbacks) {
  $panels_items = panels_page_create_menu_structure();
  if (empty($panels_items)) {
    // no point in trying to match when there is nothing to match...
    return;
  }

  // Build up an array with ONLY the items with args on native paths (overrides)
  $overrides = array_keys(array_filter($panels_items['metadata'], '_panels_page_menu_item_filter'));

  $matches = array();
  foreach ($panels_items['metadata'] as $raw_path => $metadata) {
    // Skip static panel_pages.
    if (!($metadata->load_flags & PANELS_IS_DYNAMIC)) {
      continue;
    }
    // Ensure that the path var is properly initialized on each iteration.
    $path = $raw_path;

    // Presence of a native path indicates that there are menu item properties
    // that need to be inherited.
    if (isset($metadata->native_path)) {
      if (in_array($raw_path, $overrides)) {
        // Use the native path because it will always match the overridden path
        // exactly (that is, it will always include the load function name).
        $path = $metadata->native_path;
        $matches[$path] = TRUE;
      }
      else {
        $map = explode('/', $raw_path);
        foreach ($map as $i => $arg) {
          if ($arg === '%') {
            $map[$i] = '%' . array_shift($metadata->load_functions);
          }
        }
        $path = implode('/', $map);
      }
      $panels_items['menu items'][$path] = array_merge($callbacks[$metadata->native_path], $panels_items['menu items'][$raw_path]);
      $panels_items['metadata'][$path] = $panels_items['metadata'][$raw_path];
      // Only unset if the original path  is different (e.g. taxonomy isn't)
      if ($raw_path != $path) {
        unset($panels_items['menu items'][$raw_path], $panels_items['metadata'][$raw_path]);
      }
    }
    // Update the load flags to reflect the status of a fallback router. It's
    // fine that static panel_pages miss this, they never have fallback routers.
    $metadata->load_flags |= (!empty($matches[$path])) ? PANELS_HAS_FALLBACK_ROUTER : 0;
    db_query('UPDATE {panels_page} SET load_flags = %d WHERE pid = %d', $metadata->load_flags, $metadata->pid);

    // If a special menu builder function has been defined, fire it. Mostly an
    // edge case atm, node/add is the only existing argument plugin using this
    if (isset($metadata->menu_builder)) {
      $builder_items = array();
      $func = $metadata->menu_builder;
      if ($func($builder_items, $metadata)) {
        unset($panels_items['menu items'][$metadata->path]);
      }
      while (list($key,) = each($builder_items)) {
        $builder_items[$key]['module'] = 'panels_page';
      }
      $panels_items['menu items'] = array_merge($builder_items, $panels_items['menu items']);
    }
  }

  // Insert all overridden routers into our separate router storage table, merge
  // all the panels menu items into the callback stack, then tuck the menu data
  // back into the static cache for hook_menu_link_alter() to get at later.
  _panels_page_menu_router_build($callbacks, $matches);
  $callbacks = array_merge($callbacks, $panels_items['menu items']);
  panels_page_create_menu_structure($panels_items);
}

function _panels_page_menu_item_filter($metadata) {
  if (!isset($metadata->native_path)) {
    return FALSE;
  }

  if ($metadata->path == panels_page_get_raw_path($metadata->native_path)) {
    return TRUE;
  }
  return FALSE;
}

function _panels_page_create_menu_structure() {
  panels_load_include('plugins');
  $items = array();
  $panels = panels_page_load_all();
  foreach ($panels as $panel_page) {
    if (empty($panel_page->disabled)) {
      $map = explode('/', $panel_page->path);
      if (strpos($panel_page->path, '%') === FALSE) {
        panels_page_construct_static_menu_link($items, $panel_page, $map);
      }
      // Only construct dynamic menu items if at least one argument has been
      // defined. Kinda hacky, but failure to do this check can result in really
      // icky, non-user-intuitive inconsistencies if someone creates a panel
      // page on an override path but doesn't give it an argument right away.
      elseif (!empty($panel_page->arguments)) {
        panels_page_construct_dynamic_menu_link($items, $panel_page, $map);
      }
    }
  }
  return $items;
}

function panels_page_construct_dynamic_menu_link(&$items, $panel_page, $map) {
  $type = _panels_page_menu_type($panel_page);
  $primary_wildcard = array_search('%', $map);
  // panels_page_construct_menu_item_metadata($items, $panel_page, PANELS_IS_DYNAMIC | $panel_page->load_flags);
  panels_page_construct_menu_item_metadata($items, $panel_page, PANELS_IS_DYNAMIC);
  $page_args = array($panel_page->name, $primary_wildcard);

  // Construct the dynamic menu router item. If/when we get to multiple
  // panels_pages per dynamic path, we needn't worry about overwriting here.
  _panels_page_construct_dynamic_menu_link($items, $panel_page, $page_args, $type);

  // FIXME parents are borked
  _panels_page_construct_parent_menu_item($items, $panel_page, $panel_page->path, $type);
}

/**
 * Helper function to create a menu item for a panel.
 */
function _panels_page_construct_dynamic_menu_link(&$items, $panel_page, $page_arguments, $type, $weight = 0) {
  $items['menu items'][$panel_page->path] = array(
    'title callback'  => 'panels_page_title_handler',
    'title arguments' => $page_arguments,
    // FIXME re-include an access callback/handler system; currently we just use the fallback's. Where there is no fallback... =)
    'page callback'   => 'panels_page_render_handler',
    'page arguments'  => $page_arguments,
    'type'            => $type,
    'weight'          => $weight,
    'module'          => 'panels_page',
    'file'            => NULL, // Ensure we don't get the overriddee's file property
  );

  // Add individual tab items for each of the per-display differentiated admin
  // editing areas (layout, layout settings, and content) using a static path,
  // then stack them on top of the normal admin wildcard stack using the
  // tab_root and tab_parent menu properties.
  if (!empty($panel_page->arguments)) {
    $path = "admin/panels/panel-page/$panel_page->name/edit";
    $admin = array(
      'title' => 'Default',
      'access callback' => 'panels_page_concession_to_borked_tabs',
      'access arguments' => array($panel_page->name),
      'file' => 'panels_page.admin.inc',
      'page callback' => 'panels_page_edit_alternate',
      'module' => 'panels_page',
      'tab_root' => 'admin/panels/panel-page/%/edit',
      'type' => MENU_LOCAL_TASK,
    );
    foreach ($panel_page->displays as $id => $display_conf) {
      $admin['title'] = $display_conf['title'];

      $items['menu items']["$path/layout/$id"] = array(
        'page arguments' => array('panels_page_edit_layout', 3, 6),
        'tab_parent' => $admin['tab_root'] . '/layout',
      ) + $admin;
      $items['menu items']["$path/layout-settings/$id"] = array(
        'page arguments' => array('panels_page_edit_layout_settings', 3, 6),
        'tab_parent' => $admin['tab_root'] . '/layout-settings',
      ) + $admin;
      $items['menu items']["$path/content/$id"] = array(
        'page arguments' => array('panels_page_edit_content', 3, 6),
        'tab_parent' => $admin['tab_root'] . '/content',
      ) + $admin;
    }
  }
}

/**
 * Build a panels_page menu entry for a static panels_page.
 */
function panels_page_construct_static_menu_link(&$items, $panel_page, $map) {
  $type = _panels_page_menu_type($panel_page);
  panels_page_construct_menu_item_metadata($items, $panel_page);

  $items['menu items'][$panel_page->path] = array(
    'title'            => filter_xss_admin(panels_page_get_title($panel_page, 'menu', '')),
    'access callback'  => 'panels_page_access_handler',
    'access arguments' => array($panel_page->name),
    'page callback'    => 'panels_page_render_handler',
    'page arguments'   => array($panel_page->name),
    'type'             => $type,
    'module'           => 'panels_page',
    'file'             => 'panels_page.render.inc',
  );
  _panels_page_construct_parent_menu_item($items, $panel_page, $panel_page->path, $type);
}

function panels_page_construct_menu_item_metadata(&$items, $panel_page, $load_flags = 0) {
  $metadata = new stdClass();
  $metadata->pid        = $panel_page->pid;
  $metadata->name       = $panel_page->name;
  $metadata->type       = _panels_page_menu_type($panel_page);
  $metadata->path       = $panel_page->path;
  $metadata->load_flags = $load_flags;

  if (!empty($panel_page->arguments)) {
    $metadata->load_functions = array();
    // @TODO This code assumes that the first argument in the stack is the one
    // we want to inherit from. This may or may not be a valid assumption.
    while (list($i, $arg_data) = each($panel_page->arguments)) {
      if ($i == 0) {
        $argument = panels_get_argument($arg_data['name']);
        if (!empty($argument['native path'])) {
          $metadata->native_path = $argument['native path'];
        }
        if ($function = panels_plugin_get_function('arguments', $argument['name'], 'menu builder')) {
          $metadata->menu_builder = $function;
          $metadata->arg_data = $arg_data;
        }
      }
      $metadata->load_functions[] = !empty($argument['load function']) ? $argument['load function'] : '';
    }
  }

  $items['metadata'][$panel_page->path] = $metadata;
}

/**
 * Create a parent menu item for a panel page.
 */
function _panels_page_construct_parent_menu_item(&$items, $panel_page, $path, $type) {
  if ($type == MENU_DEFAULT_LOCAL_TASK && dirname($path) && dirname($path) != '.') {
    // FIXME this is currently completely borked - if we end up inside this
    // control statement, everything will break. However, we should also be eliminating
    // the statement later.
    switch ($panel_page->menu_tab_default_parent_type) {
      case 'tab':
        $parent_type = MENU_LOCAL_TASK;
        break;

      case 'normal':
        $parent_type = MENU_NORMAL_ITEM;
        break;

      default:
      case 'existing':
        $parent_type = 0;
        break;
    }
    if ($parent_type) {
      $title   = filter_xss_admin(panels_page_get_title($panel_page, 'menu-parent'));
      $weight  = $panel_page->menu_parent_tab_weight;
      // FIXME this function doesn't even exist anymore.
      $items[$path] = _panels_page_menu_item($path, $title, $panel_page, $args, $access, $parent_type, $weight);
    }
  }
}

/**
 * Determine what menu type a panel needs to use.
 */
function _panels_page_menu_type($panel_page) {
  if ($panel_page->menu) {
    if ($panel_page->menu_tab_default) {
      $type = MENU_DEFAULT_LOCAL_TASK;
    }
    else if ($panel_page->menu_tab) {
      $type = MENU_LOCAL_TASK;
    }
    else {
      $type = MENU_NORMAL_ITEM;
    }
  }
  else {
    $type = MENU_CALLBACK;
  }
  return $type;
}

function panels_page_router_table_query_fields($table) {
  static $query_building_blocks = array();
  if (empty($query_building_blocks[$table])) {
    $schema = drupal_get_schema($table);
    foreach ($schema['fields'] as $field => $data) {
      $query_building_blocks[$table][$field] = db_type_placeholder($data['type']);
    }
  }
  return $query_building_blocks[$table];
}

function panels_page_get_raw_path($path) {
  return preg_replace('/%([a-z_]*)/', '%', $path);
}

/**
 * Modified version of _menu_router_build();
 */
function _panels_page_menu_router_build($callbacks, $panels_matches) {
  $menu = array();
  $sort = array();
  foreach ($panels_matches as $path => $match) {
    $item = $callbacks[$path];
    $load_functions = array();
    $to_arg_functions = array();
    $fit = 0;
    $move = FALSE;

    $parts = explode('/', $path, MENU_MAX_PARTS);
    $number_parts = count($parts);
    // We store the highest index of parts here to save some work in the fit
    // calculation loop.
    $slashes = $number_parts - 1;
    // Extract load and to_arg functions.
    foreach ($parts as $k => $part) {
      $match = FALSE;
      if (preg_match('/^%([a-z_]*)$/', $part, $matches)) {
        if (empty($matches[1])) {
          $match = TRUE;
          $load_functions[$k] = NULL;
        }
        else {
          if (function_exists($matches[1] .'_to_arg')) {
            $to_arg_functions[$k] = $matches[1] .'_to_arg';
            $load_functions[$k] = NULL;
            $match = TRUE;
          }
          if (function_exists($matches[1] .'_load')) {
            $function = $matches[1] .'_load';
            // Create an array of arguments that will be passed to the _load
            // function when this menu path is checked, if 'load arguments'
            // exists.
            $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function;
            $match = TRUE;
          }
        }
      }
      if ($match) {
        $parts[$k] = '%';
      }
      else {
        $fit |=  1 << ($slashes - $k);
      }
    }
    if ($fit) {
      $move = TRUE;
    }
    else {
      // If there is no %, it fits maximally.
      $fit = (1 << $number_parts) - 1;
    }
    $masks[$fit] = 1;
    $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
    $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
    $item += array(
      'title' => '',
      'weight' => 0,
      'type' => MENU_NORMAL_ITEM,
      '_number_parts' => $number_parts,
      '_parts' => $parts,
      '_fit' => $fit,
    );
    $item += array(
      '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_BREADCRUMB),
      '_tab' => (bool)($item['type'] & MENU_IS_LOCAL_TASK),
    );
    if ($move) {
      $new_path = implode('/', $item['_parts']);
      $menu[$new_path] = $item;
      $sort[$new_path] = $number_parts;
    }
    else {
      $menu[$path] = $item;
      $sort[$path] = $number_parts;
    }
  }
  array_multisort($sort, SORT_NUMERIC, $menu);
  if (!$menu) {
    // We must have a serious error - there is no data to save.
    watchdog('php', 'The specialized Panels menu router rebuild failed. Some paths may not work correctly.', array(), WATCHDOG_ERROR);
    return array();
  }
  db_query('DELETE FROM {panels_page_router_store}');
  foreach ($menu as $path => $v) {
    $item = &$menu[$path];
    if (!$item['_tab']) {
      // Non-tab items.
      $item['tab_parent'] = '';
      $item['tab_root'] = $path;
    }
    for ($i = $item['_number_parts'] - 1; $i; $i--) {
      $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
      if (isset($menu[$parent_path])) {

        $parent = $menu[$parent_path];

        if (!isset($item['tab_parent'])) {
          // Parent stores the parent of the path.
          $item['tab_parent'] = $parent_path;
        }
        if (!isset($item['tab_root']) && !$parent['_tab']) {
          $item['tab_root'] = $parent_path;
        }
        // If an access callback is not found for a default local task we use
        // the callback from the parent, since we expect them to be identical.
        // In all other cases, the access parameters must be specified.
        if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) {
          $item['access callback'] = $parent['access callback'];
          if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
            $item['access arguments'] = $parent['access arguments'];
          }
        }
        // Same for page callbacks.
        if (!isset($item['page callback']) && isset($parent['page callback'])) {
          $item['page callback'] = $parent['page callback'];
          if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
            $item['page arguments'] = $parent['page arguments'];
          }
          if (!isset($item['file']) && isset($parent['file'])) {
            $item['file'] = $parent['file'];
          }
          if (!isset($item['file path']) && isset($parent['file path'])) {
            $item['file path'] = $parent['file path'];
          }
        }
      }
    }
    if (!isset($item['access callback']) && isset($item['access arguments'])) {
      // Default callback.
      $item['access callback'] = 'user_access';
    }
    if (!isset($item['access callback']) || empty($item['page callback'])) {
      $item['access callback'] = 0;
    }
    if (is_bool($item['access callback'])) {
      $item['access callback'] = intval($item['access callback']);
    }

    $item += array(
      'access arguments' => array(),
      'access callback' => '',
      'page arguments' => array(),
      'page callback' => '',
      'block callback' => '',
      'title arguments' => array(),
      'title callback' => 't',
      'description' => '',
      'position' => '',
      'tab_parent' => '',
      'tab_root' => $path,
      'path' => $path,
      'file' => '',
      'file path' => '',
      'include file' => '',
    );

    // Calculate out the file to be included for each callback, if any.
    if ($item['file']) {
      $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
      $item['include file'] = $file_path .'/'. $item['file'];
    }

    $title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : '';
    db_query("INSERT INTO {panels_page_router_store}
      (path, load_functions, to_arg_functions, access_callback,
      access_arguments, page_callback, page_arguments, fit,
      number_parts, tab_parent, tab_root,
      title, title_callback, title_arguments,
      type, block_callback, description, position, weight, file)
      VALUES ('%s', '%s', '%s', '%s',
      '%s', '%s', '%s', %d,
      %d, '%s', '%s',
      '%s', '%s', '%s',
      %d, '%s', '%s', '%s', %d, '%s')",
      $path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'],
      serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'],
      $item['_number_parts'], $item['tab_parent'], $item['tab_root'],
      $item['title'], $item['title callback'], $title_arguments,
      $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']);
  }
}
